
const stringEnd = (s, begin) => {
    // begin 是字符串 s 开始切片的位置
    // 也就是说从 begin 的位置开始遍历 s
    // 直到遇到空格
    // 返回字符串和下标遍历的次数(用数组的形式)
    // 如果不理解下标遍历的次数就看看测试用例
    let str = ""
    let i = begin
    let symbols = "()={}[],."
    if("tnfcvlao".includes(s[i])) {
        let rest = s.slice(i)
        let key = keyword(rest)
        if(key){
            return key
        }

    }
    for (i; i < s.length; i++) {
        if(s[i] == "\n"  || s[i] === " " || symbols.includes(s[i])){
            i -= 1
            break
        }
        str += s[i]
    }
    let len = i - begin + 1
    let res = [str, len, TokenType.variable]

    return res
    // 字符串结尾可能不是空格（比如第三个测试用例的 asdf）
    // 最简单的做法是, 先在末尾加个空格再解析
    // 你知道很多编译器必须要求最后一个字符是回车键吗？
}

const numberEnd = (s, begin) => {
    // begin 是字符串 s 开始切片的位置
    // 也就是说从 begin 的位置开始遍历 s
    // 直到遇到空格
    // 返回数字和下标遍历的次数(用数组的形式)
    // 如果不理解下标遍历的次数就看看测试用例
    let str = ""
    let i = begin
    let symbols = "()={}[],=<>!+-*%/"
    for (i; i < s.length; i++) {
        if (s[i] === " " || s[i] == "\n"  || symbols.includes(s[i])) {
            i -= 1
            break
        }
        str += s[i]
    }
    let len = i - begin + 1

    let res = [Number(str), len, TokenType.number]

    return res

}
const quoteStringEnd = (s, index, quote) => {
    let str = ""
    let _mapper = {
        'a': '\a',
        'b': '\b',
        'f': '\f',
        'n': '\n',
        'r': '\r',
        't': '\t',
        'v': '\v',
        '0': '\0',
        '\\': '\\',
        '"': "\"",
        "/": "\/",
        "'": "\'",
    }
    let i = index

    for (i; i < s.length; i++) {
        if (s[i] === quote) {
            break
        } else if (s[i] === "\\") {
            let next = s[i + 1]
            let keys = Object.keys(_mapper)
            if (keys.includes(next)) {
                str += _mapper[next]
                i += 1
            }
        } else {
            str += s[i]
        }
    }
    let len = i - index + 1
    let res = [str, len, TokenType.string]
    return res
}

const keyword = (s) => {
    // s 是一个包含 boolean 类型或者 null 的字符串
    // 解析并返回对应的值(boolean 类型或者 null)
    //const class function var let

    if (s[0] === "t" && s.slice(0, 4) === "true") {
        return [true, 4, TokenType.boolean]
    }
    if (s[0] === "f" && s.slice(0, 5) === "false") {
        return [false, 5,  TokenType.boolean]
    }
    if (s[0] === "n" && s.slice(0, 4) === "null") {
        return [null, 4, TokenType.null]
    }
    if (s[0] === "c" && s.slice(0, 5) === "const") {
        return ["const", 5, TokenType.keyword]
    }
    if (s[0] === "v" && s.slice(0, 3) === "var") {
        return ["var", 3, TokenType.keyword]
    }
    if (s[0] === "l" && s.slice(0, 3) === "let") {
        return ["let", 3, TokenType.keyword]
    }
    if (s[0] === "c" && s.slice(0, 5) === "class") {
        return ["class", 5, TokenType.keyword]
    }
    if (s[0] === "f" && s.slice(0, 7) === "function") {
        return ["function", 7, TokenType.keyword]
    }
    if (s[0] === "a" && s.slice(0, 3) === "and") {
        return ["and", 3, TokenType.and]
    }
    if (s[0] === "o" && s.slice(0, 2) === "or") {
        return ["or", 2, TokenType.or]
    }
    if (s[0] === "n" && s.slice(0, 3) === "not") {
        return ["not", 3, TokenType.not]
    }
    return false
}
const symbolStringEnd = (s, begin) => {
    let str = ""
    let i = begin
    let symbols = ",.=<>!+-*%/"
    for (i; i < s.length; i++) {
        if (s[i] === " "|| !symbols.includes(s[i])) {
            i -= 1
            break
        }else{
            str += s[i]
        }
    }
    let len = i - begin + 1
    let type = TokenType.auto
    let _mapper = {
        ":": TokenType.colon,
        ",": TokenType.comma,
        "=": TokenType.equal,
        ";": TokenType.assign,
        "!=": TokenType.notEqual,
        ">": TokenType.greateThan,
        "<": TokenType.lessThan,
        ">=": TokenType.greatEqual,
        "<=": TokenType.lessEqual,
        "+=": TokenType.assignPlus ,
        "-=": TokenType.assignMinus,
        "*=": TokenType.assignMultiply ,
        "/=": TokenType.assignDivide,
        "+": TokenType.plus,
        "*": TokenType.multiply,
        "/": TokenType.divide,
        "%": TokenType.mod,
        ".": TokenType.dot,
    }
    if(Object.keys(_mapper).includes(str)){
        type = _mapper[str]
    }
    let res = [str, len, type]
    return res
}
const bracketEnd = (b) => {
    if(b === "["){
        return ["[", 1, TokenType.leftBracket]
    }else if(b === "]"){
        return ["]", 1, TokenType.rightBracket]
    }else if(b === "("){
        return ["(", 1, TokenType.leftParentheses]
    }else if(b === ")"){
        return [")", 1, TokenType.rightParentheses]
    }else if(b === "{"){
        return ["{", 1, TokenType.leftCurly]
    }else if(b === "}"){
        return ["}", 1, TokenType.rightCurly]
    }
}

const toTokens = (s) => {

    // 提取出字符串 s 中的字符串
    let letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
    let _number = '0123456789'
    let symbols = "=,.<>!+-*/%"
    let bracket = "(){}[]"
    // s += " "
    let res = []
    let i = 0
    let row = 1
    let col = 1
    for (i; i < s.length; i++) {
        if (letters.includes(s[i])) {
            let rest = s.slice(i)
            let str = stringEnd(rest, 0)
            let token = new Token(str[0], str[2], row, col, row, col+str[1])
            res.push(token)

            col += str[1]
            i += str[1] - 1

        }else if (_number.includes(s[i]) || s[i] === "-") {
            // 如果只是减号的情况
            if(s[i] === "-" && s[i+1] === "="){
                let token = new Token("-=", TokenType.assignMinus,  row, col, row, col+2)
                res.push(token)
                i += 1
                col += 2

            }else if(s[i] === "-" && !_number.includes(s[i+1])){
                let token = new Token(s[i], TokenType.minus,  row, col, row, col+1)
                res.push(token)

                col += 1

            }else {
                let rest = s.slice(i)
                let num = numberEnd(rest, 0)
                let token = new Token(num[0], num[2], row, col, row, col+num[1])
                res.push(token)

                col += num[1]
                i += num[1] - 1
            }

        } else if (s[i] === "\'" || s[i] === '\"') {
            let rest = s.slice(i + 1)
            let quoteStr = quoteStringEnd(rest, 0, s[i])
            let token = new Token(quoteStr[0], quoteStr[2], row, col, row, col+quoteStr[1])
            res.push(token)
            col += quoteStr[1] + 2
            i += quoteStr[1] + 1 - 1
        } else if (symbols.includes(s[i])){
            let rest = s.slice(i)
            let symbol = symbolStringEnd(rest, 0)
            let token = new Token(symbol[0], symbol[2], row, col, row, col+symbol[1])
            res.push(token)
            col += symbol[1]
            i += symbol[1] - 1
        } else if (bracket.includes(s[i])){
            // log("s[i]", s[i])
            let brackets = bracketEnd(s[i])
            col += 1
            // log("bracket", brackets)

            let token = new Token(brackets[0], brackets[2], row, col, row, col+brackets[1])
            res.push(token)
        }else if (s[i] === "\n") {
            let token = new Token(";", TokenType.semicolon, row, col, row, col+1)
            res.push(token)
            row += 1
            col = 1
        }else if (s[i] === " ") {
            col += 1
        }
    }
    return res

}
// this.tokenValue = value
// this.tokenType = type
// this.startRow = startRow
// this.startCol = startCol
// this.endRow = endRow
// this.endCol = endCol
class Token {
    constructor(tokenValue, tokenType, startRow, startCol, endRow, endCol) {
        this.tokenValue = tokenValue
        this.tokenType = tokenType
        this.startRow = startRow
        this.startCol = startCol
        this.endRow = endRow
        this.endCol = endCol
    }

}
const testAssign = () => {
    let s1 = String.raw` 
    a += 1
    a *= 2
    a /= 3
    a -= 5
    `
    log("源码：", s1)
    log("toTokens", toTokens(s1))
}
const __main = () => {
//     let s1 = String.raw`const Animal = class() {
// const new = function(sex, age) {
// const this.age = age
// const a = "hello hahsfa"
// this.sex = "female"
// this.age = 20
// }
// }
// `
//     testAssign()
}

__main()